#pragma once

#include<iostream>
#include<unistd.h>
#include<string>
#include<vector>
#include<sstream>
#include<unordered_map>
#include<sys/stat.h>
#include<sys/sendfile.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<algorithm>

#include"Util.hpp"
#include"Log.hpp"


const char* SEP=": ";
const std::string WEBROOT="wwwroot";
const std::string HOMEPAGE="index.html";
const std::string HTTP_VERSION="HTTP/1.1";
const std::string LINE_END="\r\n";
const std::string PAGE_404="404.html";

enum Status
{
    OK=200,
    BADREQUEST=400,
    NOTFOUND=404,
    SERVERERROR=500
};
static std::string CodeToDesc(int code)
{
    std::string ret;
    switch (code)
    {
        case Status::OK:
            ret="OK";
            break;
        case Status::NOTFOUND:
            ret="NOTFOUND";
            break;
    
        default:
            break;
    }
    return ret;
}

static std::string SuffixToDesc(const std::string&suffix)
{
    static std::unordered_map<std::string,std::string> suffix2desc=
    {{".html","text/html"},
    {".css","text/css"},
    {".js","application/javascript"},
    {".jpg","application/x-jpg"},
    {".xml","application/xml"},
    {".pdf","application/pdf"}
    };

    auto it=suffix2desc.find(suffix);
    if(it!=suffix2desc.end())
    {
        return it->second;
    }
    return "text/html";
}

class HttpRequest
{
public:
    std::string request_line;
    std::vector<std::string> request_header;
    std::string blank;
    std::string request_body;

    std::string method;
    std::string uri;
    std::string version;

    std::unordered_map<std::string,std::string> header_kv;
    int content_length=0;

    std::string path;
    std::string query_string;
    std::string suffix;

    bool cgi=false;
};
class HttpResponse
{
public:
    std::string status_line;
    std::vector<std::string> response_header;
    std::string blank=LINE_END;
    std::string response_body;

    int status_code=Status::OK;
    int fd=-1;
    int size=0;
};


class EndPoint
{
private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
    bool stop;

private:
    bool RecvHttpRequestLine()
    {
        if(Util::ReadLine(sock,http_request.request_line)>0)
        {
            http_request.request_line.pop_back();
            LOG(INFO,http_request.request_line);
        }else
        {
            stop=true;
        }
        return stop;
    }
    bool RecvHttpRequestHeader()
    {
        std::string line;
        while(true)
        {
            line.clear();
            if(Util::ReadLine(sock,line)<=0)
            {
                stop=true;
                break;
            }
            if(line=="\n")
            {
                http_request.blank=line;
                break;
            }
            line.pop_back();
            http_request.request_header.push_back(line);
            LOG(INFO,line);
        }
        return stop;
    }
    void ParseHttpRequestLine()
    {
        auto line=http_request.request_line;
        std::stringstream ss(line);
        ss>>http_request.method>>http_request.uri>>http_request.version; 
        std::transform(http_request.method.begin(),http_request.method.end(),http_request.method.begin(),::toupper);
        // LOG(INFO,http_request.method);
        // LOG(INFO,http_request.uri);
        // LOG(INFO,http_request.version);
    }
    void ParseHttpRequestHeader()
    {
        std::string key;
        std::string value;
        for(auto& iter:http_request.request_header)
        {
            key.clear();
            value.clear();
            if(Util::CutString(iter,key,value,SEP))
                http_request.header_kv.insert({key,value});
        }
    }
    bool  IsNeedRecvHttpRequestBody()
    {
        auto method=http_request.method;
        if(method=="POST")
        {
            auto& header_kv=http_request.header_kv;
            auto it=header_kv.find("Content-Length");
            if(it!=header_kv.end())
            {
                http_request.content_length=atoi(it->second.c_str());
                return true;
            }
        }
        return false;
    }
    bool RecvHttpRequestBody()
    {
        if(IsNeedRecvHttpRequestBody())
        {
            int content_length=http_request.content_length;
            auto& body=http_request.request_body;
            char ch=0;
            while(content_length)
            {
                ssize_t s=recv(sock,&ch,1,0);
                if(s>0)
                {
                    body+=ch;
                    content_length--;
                }else 
                {
                    stop=true;
                    break;
                }
            }
            LOG(INFO,http_request.request_body);
        }
        return stop;
    }
    int ProcessNonCgi()
    {
        http_response.fd=open(http_request.path.c_str(),O_RDONLY);
        if(http_response.fd>=0)
        {
        //创建响应行
            return Status::OK;
        }
        return Status::NOTFOUND;
    }
    int ProcessCgi()
    {
        auto& method=http_request.method;
        auto& query_string=http_request.query_string; //GET
        auto& body_text=http_request.request_body;    //POST
        int code=Status::OK;
        auto& bin=http_request.path;
        int content_length=http_request.content_length;
        std::string query_string_env;
        std::string method_env;
        std::string content_length_env;
        

        //std::cout<<"bin: "<<bin<<std::endl;
        //站在父进程角度
        int input[2];
        int output[2];
        if(pipe(input)<0)
        {
            LOG(ERROR,"pipe input error");
            code=Status::SERVERERROR;
            return code;
        }
        if(pipe(output)<0)
        {
            LOG(ERROR,"pipe output error");
            code=Status::SERVERERROR;
            return code;
        }

        pid_t pid=fork();
        if(pid==0)
        {
            //child execl
            close(input[0]);
            close(output[1]);
            method_env="METHOD=";
            method_env+=method;
            putenv((char*)method_env.c_str());
            if(method=="GET")
            {
                query_string_env="QUERY_STRING=";
                query_string_env+=query_string;
                putenv((char*)query_string_env.c_str());
            }else if(method=="POST")
            {
                content_length_env="CONTENT_LENGTH=";
                content_length_env+=std::to_string(content_length);
                putenv((char*)content_length_env.c_str());
                LOG(INFO,"POST method ,add content_length_env");
            }else
            {
                code=Status::NOTFOUND;
            }
            dup2(output[0],0);
            dup2(input[1],1);
            execl(bin.c_str(),bin.c_str(),nullptr);
            exit(1);
        }else if(pid<0)
        {
            LOG(ERROR,"fork error");
            return Status::NOTFOUND;
        }else
        {
            //parent
            close(input[1]);
            close(output[0]);
            if(method=="POST")
            {
                //LOG(INFO,"method is post");
                size_t size=0;
                size_t total=0;
                const char* start=body_text.c_str();
                while((total<content_length)&&(size=write(output[1],start+total,body_text.size()-total)))
                {
                    total+=size;
                }
                //LOG(INFO,"send body");
            }
            char ch=0;
            while(read(input[0],&ch,1))
            {
                http_response.response_body+=ch;
            }

            int status=0;
            pid_t ret= waitpid(pid,&status,0);
            if(pid==ret)
            {
                if(WIFEXITED(status))
                {
                    if(WEXITSTATUS(status)==0)
                    {
                        code=Status::OK;
                    }else
                    {
                        code=Status::BADREQUEST;
                    }
                }else
                {
                    code=Status::SERVERERROR;
                }
            }

            close(input[0]);
            close(output[1]);
        }
        return code;
    }

    void HandlerError(std::string page)
    {
        http_request.cgi=false;
        //返回404页面
        std::cout<<page<<std::endl;
        http_response.fd=open(page.c_str(),O_RDONLY);
        if(http_response.fd>0)
        {
            struct stat s;
            stat(page.c_str(),&s);
            std::string line="Content-Type: text/html";
            line+=LINE_END;
            http_response.response_header.push_back(line);
            line="Content-Length: ";
            line+=std::to_string(s.st_size);
            line+=LINE_END;
            http_response.response_header.push_back(line);
            http_response.size=s.st_size;
        }
    }
    void BuildOKResponce()
    {
        std::string line="Content-Type: ";
        line+=SuffixToDesc(http_request.suffix);
        line+=LINE_END;
        http_response.response_header.push_back(line);
        line="Content-Length: ";
        if(http_request.cgi)
        {
            line+=std::to_string(http_response.response_body.size());
        }else
        {
            line+=std::to_string(http_response.size);
        }
        
        line+=LINE_END;
        http_response.response_header.push_back(line);
    }
    void BuildHttpResponseHelper()
    {
        auto& code=http_response.status_code;
        //根据状态码构建状态行
        auto&status_line=http_response.status_line;
        status_line+=HTTP_VERSION;
        status_line+=" ";
        status_line+=std::to_string(code);
        status_line+=" ";
        status_line+=CodeToDesc(code);
        status_line+=LINE_END;

        //构建响应正文
        std::string path=WEBROOT;
        path+='/';
        
        switch(code)
        {
            case Status::OK:
                BuildOKResponce();
                break;
            case Status::BADREQUEST:
                path+=PAGE_404;
                HandlerError(path);
                break;
            case Status::NOTFOUND:
                path+=PAGE_404;
                HandlerError(path);
                break;
            case Status::SERVERERROR:
                path+=PAGE_404;
                HandlerError(path);
                break;
            default:
                break;
        }
    }
public:
    EndPoint(int _sock):sock(_sock),stop(false)
    {}

    bool IsStop()
    {
        return stop;
    }
    void RecvHttpRequest()
    {
        if((!RecvHttpRequestLine())&&(!RecvHttpRequestHeader()))
        {
            ParseHttpRequestLine();
            ParseHttpRequestHeader();
            RecvHttpRequestBody();
        }
    }
    void BuildHttpResponse()
    {
        std::string _path;
        auto& code=http_response.status_code;
        int size=0;
        std::size_t found=0;
        if(http_request.method!="GET"&&http_request.method!="POST")
        {
            LOG(WARNING,"Request method error");
            code=Status::BADREQUEST;
            goto END;
        }
        if(http_request.method=="GET")
        {
            ssize_t pos=http_request.uri.find('?');
            if(pos!=std::string::npos)
            {
                Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?");
                http_request.cgi=true;
            }else{
                http_request.path=http_request.uri;
            }
            _path=http_request.path;
            http_request.path=WEBROOT;
            http_request.path+=_path;
            
            if(http_request.path[http_request.path.size()-1]=='/')
            {
                http_request.path+=HOMEPAGE;
            }
            std::cout<<"path=: "<<http_request.path<<std::endl;
            
            std::cout<<http_request.query_string<<std::endl;
        }else if(http_request.method=="POST")
        {
            //POST
            http_request.path=http_request.uri;
            _path=http_request.path;
            http_request.path=WEBROOT;
            http_request.path+=_path;
            LOG(INFO,"cgi set true");
            http_request.cgi=true;
        }else
        {

        }
        struct stat s;
        
        if(stat(http_request.path.c_str(),&s)==0)//资源存在
        {
            if(S_ISDIR(s.st_mode))
            {
                http_request.path+='/';
                http_request.path+=HOMEPAGE;
                stat(http_request.path.c_str(),&s);
            }
            if((S_IXUSR&s.st_mode)||(S_IXGRP&s.st_mode)||(S_IXOTH&s.st_mode))
            {
                //cgi
                http_request.cgi=true;
            }
            http_response.size=s.st_size;
            
        }else
        {
            LOG(WARNING,http_request.path+"NOT FOUND");
            code=Status::NOTFOUND;
            goto END;
        }
        found=http_request.path.rfind('.');
        if(found==std::string::npos)
        {
            http_request.suffix=".html";
        }else
        {
            http_request.suffix=http_request.path.substr(found);
        }

        if(http_request.cgi)
        {
            //Process cgi
            code=ProcessCgi();
        }else
        {
            //1.目标网页一定存在
            code=ProcessNonCgi();//返回静态网页
        }

END:
       
        BuildHttpResponseHelper();
    }
    void SendHttpResponse()
    {
        send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);
        for(auto& iter:http_response.response_header)
        {
            send(sock,iter.c_str(),iter.size(),0);
        }
        send(sock,http_response.blank.c_str(),http_response.blank.size(),0);
        
        if(http_request.cgi)
        {
            int total=0;
            int size=http_response.response_body.size();
            int s=0;
            const char*start=http_response.response_body.c_str();
            while((total<size)&&(s=send(sock,start+total,size-total,0)))
            {
                total+=s;
            }
            LOG(INFO,"send success!");
        }else
        {
            sendfile(sock,http_response.fd,nullptr,http_response.size);
            close(http_response.fd);
        }
        
    }

    ~EndPoint()
    {
        close(sock);
    }

};


class CallBack
{
public:
    CallBack()
    {}
    void operator()(int sock)
    {
        HandleRequest(sock);
    }
    void HandleRequest(int sock)
    {
        LOG(INFO,"Handler Request...");
#ifdef DEBUG
        char buffer[4096];
        int n=recv(sock,buffer,sizeof(buffer)-1,0);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<buffer<<std::endl;
        }
#else

    EndPoint*ep=new EndPoint(sock);
    ep->RecvHttpRequest();
    if(!ep->IsStop())
    {
        LOG(INFO,"Recv No error,build and send response");
        ep->BuildHttpResponse();
        ep->SendHttpResponse();
    }else
    {
        LOG(WARNING,"Recv error");
    }
   
    delete ep;
#endif
    
    }
    ~CallBack()
    {}
};